Skip to main content

AMSI Primer

AMSI - Antimalware Scan Interface


Carlos Diaz

Carlos Diaz

@dfirence


Overview

From a defensive standpoint, let's discuss and learn AMSI!

But first, let's align with the standard definition by the creators of AMSI at Microsoft Corporation.


Definition - By Microsoft Corporation

The Windows Antimalware Scan Interface (AMSI) is a versatile interface standard that allows your applications and services to integrate with any antimalware product that's present on a machine.

AMSI provides enhanced malware protection for your end-users and their data, applications, and workloads.


What is AMSI?

AMSI is Visibility

AMSI is basically another security visibility event source. Specifically, it is one of several resources that can allow defenders to gain detection capabilities of script based code. For example, vbScript or jScript code that would be executed within office applications or scriptlets can be inspected and matched with basic string pattern matching operators.

AMSI Visibility Source

Because AMSI is an interface offered by the Windows Operating System, it is accessed through code.

The general idea is that script based strings are extracted by the operating system and given to the AMSI component, then a subscriber can read the contents from the AMSI component, and consequently apply pattern matches against the contents.


What does AMSI do?

Basically it is a mechanism that can inform a detection/prevention component that some data (strings) matched an interesting pattern, and by consequence the detection/prevention component can take an action. By action we mean the way common security products apply a restriction (preventing), or notifying (reporting).

Security Awareness

AMSI is not the only mechanism used on a device to determine or identify the presence of malware. It is one of many layers in the overall design of such solutions.

AMSI Notifies - Mainly

AMSI does not block, prevent, disrupt, or by any means restrict anything in the operating system. It scans and reports a match from declared patterns.

Let's explore how this all works conceptually, starting with a standard FILE_CREATION transaction from a fake application FOO.EXE.

Flow Sequence - FILE CREATION MONITORED BY AV_SECURITY_PRODUCT


Kernel Drivers - Disrupt

Since most of the needs to restrict something in Windows relates to special things called - Executive Objects, then it is a kernel-driver that can intercept those objects and apply a restriction before these objects are created.

Now, let's illustrate the way AMSI and KERNEL Drivers perform security restrictions based on a matching condition for the string "HelloWorld" being written into a fake file called FILE_TXT.


Flow Sequence - FOO.EXE WRITES CONTENT INTO FILE & DISRUPTION OCCURS


How is AMSI implemented?

Microsoft offers a programmatic API where software engineers can create AMSI Providers. Conceptually, an AMSI Provider is a program typically created by vendors of security solutions offering business value to disrupt the presence of malicious threats.

We will look at the conceptual usage of the Microsoft AMSI API functions and create some experiments to learn about AMSI.

AMSI API

The AMSI API is fairly simple and the table below briefly describes what each AMSI function does. Pay special attention to the Function Signature, we can derive an intuitive understanding that there are 2 crucial structures when using the AMSI API, those being the HAMSICONTEXT and HAMSISESSION.


AMSI API FunctionFunction PurposeFunction Signature
AmsiCloseSessionClose a session that was opened by AmsiOpenSession.
void AmsiCloseSession(
[in] HAMSICONTEXT amsiContext,
[in] HAMSISESSION amsiSession
);
AmsiInitializeInitialize the AMSI API.
HRESULT AmsiInitialize(
[in] LPCWSTR appName,
[out] HAMSICONTEXT *amsiContext
);
AmsiNotifyOperationSends to the antimalware provider a notification of an arbitrary operation.
HRESULT AmsiNotifyOperation(
[in] HAMSICONTEXT amsiContext,
[in] HAMSISESSION amsiSession,
[in] AMSI_RESULT amsiResult,
[in] ULONG_PTR *args
);
AmsiOpenSessionOpens a session within which multiple scan requests can be correlated.
HRESULT AmsiOpenSession(
[in] HAMSICONTEXT amsiContext,
[out] HAMSISESSION *amsiSession
);
AmsiResultIsMalwareDetermines if the result of a scan indicates that the content should be blocked.
BOOL AmsiResultIsMalware(
[in] AMSI_RESULT result
);
AmsiScanBufferScans a buffer-full of content for malware.
HRESULT AmsiScanBuffer(
[in] HAMSICONTEXT amsiContext,
[in] PVOID buffer,
[in] ULONG length,
[in] LPCWSTR contentName,
[in, optional] HAMSISESSION amsiSession,
[out] AMSI_RESULT *result
);
AmsiScanStringScans a string for malware.
HRESULT AmsiScanString(
[in] HAMSICONTEXT amsiContext,
[in] LPCWSTR string,
[in] LPCWSTR contentName,
[in, optional] HAMSISESSION amsiSession,
[out] AMSI_RESULT *result
);
AmsiUninitializeRemove the instance of the AMSI API that was originally opened by AmsiInitialize.
void AmsiUninitialize(
[in] HAMSICONTEXT amsiContext
);

AMSI API via AMSI.DLL

The Microsoft Corporation provides the AMSI API through a library (DLL). This DLL has exports, and the functions of the API are prefixed with Amsi*.

Using my parser pe-compass, we can see the exports below from the 64Bit version of amsi.dll.

JSON - AMSI DLL Exports
{
"name": "amsi.dll",
"path": "\\\\?\\C:\\Windows\\System32\\amsi.dll",
"size": 111104,
"is_64": true,
"is_lib": true,
"is_dotnet": false,
"has_imports": true,
"has_exports": true,
"subsystem": 2,
"subsystem_caption": "The Windows Graphical User Interface (GUI) Subsystem",
"exports": {
"count": 13,
"functions": [
"AmsiCloseSession", // Close a session opened by AmsiOpenSession
"AmsiInitialize", // Initialize the AMSI API
"AmsiOpenSession", // Opens a session for multiple scan requests
"AmsiScanBuffer", // Scans a buffer-full of content for malware
"AmsiScanString", // Scans a string for malware.
"AmsiUacInitialize",
"AmsiUacScan",
"AmsiUacUninitialize",
"AmsiUninitialize", // Clears initialized AMSI Session
"DllCanUnloadNow",
"DllGetClassObject",
"DllRegisterServer",
"DllUnregisterServer"
]
},
"hashes": {
"sha2": "9267a786db2f180128f7a0e6d668f03e6348f85f5fe91465bc2f5a341c13d81c",
"ssdeep": "1536:M5Z6rNbz+pwTP2bzlmxX4suz0qL+FM6iuS4av25Vt+F+AQOSNy+Q/9ipFj2Nc8jk:M6MqbscczXuHiwE+fNylcRscgDRST"
}
}

AMSI API Mental Model

When designing a research tool or product prototype, we can start outlining 5 steps to use the AMSI API.

SDLC - SAFE CODE

Ensure SAFE resource handling - you must use: AmsiCloseSession, AmsiUninitialize

AMSI API in C++

Using CPP or C++ language, a developer would use the AMSI API from the header file <amsi.h>.

Click To Expand: C++ AMSI Conceptual Implementation
amsi_concept.exe - Applied usage of AMSI API
#include <windows.h>
#include <amsi.h>
#include <iostream>
#include <string>

/**
* ShowBanner - Displays the program's banner.
*
* This function prints a banner to the console that includes
* the name of the program and some decorative dashes.
*/
void ShowBanner()
{
std::string dashes(64, '-'); // Create a string with 64 dashes
std::string header = "\n" + dashes + "\n";
std::string pname = header + "Cyballistics - amsi_concept.exe" + "\n" + dashes;
std::cout << pname << std::endl;
}

/**
* IsMalware - Checks if the scan result indicates malware.
*
* @param scan_result The result of the AMSI scan.
* @return True if the result indicates malware, otherwise false.
*/
bool IsMalware(AMSI_RESULT scan_result)
{
return (scan_result == AMSI_RESULT_DETECTED);
}

/**
* HResultToString - Converts an HRESULT value to a readable string.
*
* @param hr The HRESULT value to convert.
* @return A string representing the error message associated with the HRESULT.
*/
std::string HResultToString(HRESULT hr)
{
char* errorMsg = nullptr;
DWORD size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&errorMsg, 0, nullptr);

std::string errorString;
if (size)
{
errorString = errorMsg;
LocalFree(errorMsg);
}
else
{
errorString = "Unknown error";
}

return errorString;
}

int main()
{
HAMSICONTEXT amsiContext = nullptr;
HAMSISESSION amsiSession = nullptr;
AMSI_RESULT amsiResult;
HRESULT hrStatus;

LPCWSTR amsiAppName = L"DefenderSquad-AMSI-Provider";
LPCWSTR amsiContentName = L"TestContent";

std::string msg = "";
ShowBanner(); // Display the banner

// Our Test Buffer For AMSI Engine
const char* testBuffer = "Hello From Evil World";
ULONG bufferSize = (ULONG)strlen(testBuffer);

// Step 1: Initialize AMSI
hrStatus = AmsiInitialize(amsiAppName, &amsiContext);
if (FAILED(hrStatus))
{
msg = "Unable to initialize AMSI: " + HResultToString(hrStatus);
goto exitEarly;
}

// Step 2: Open New AMSI Session
hrStatus = AmsiOpenSession(amsiContext, &amsiSession);
if (FAILED(hrStatus))
{
msg = "Unable to open new AMSI session: " + HResultToString(hrStatus);
goto exitProgram;
}

// Step 3: Scan the buffer
hrStatus = AmsiScanBuffer(amsiContext,
(PVOID)testBuffer,
bufferSize,
amsiContentName,
amsiSession,
&amsiResult);
if (FAILED(hrStatus))
{
msg = "[error] - AmsiScanBuffer: " + HResultToString(hrStatus);
goto exitProgram;
}

// Step 4: Check Scan Result Status
if (IsMalware(amsiResult))
{
msg = "Scan Result: MALWARE DETECTION\n";
}
else
{
msg = "Scan Result: CLEAN VERDICT\n";
}
msg += "\nBuffer Size: " + std::to_string(bufferSize) + "\nBuffer Content: " + testBuffer;

// Leaves Early, No Cleanup Needed
exitEarly:
std::cout << msg << "Early Exit" << std::endl;
return 1;

// Proper AMSI Cleanup
exitProgram:
if (amsiSession)
{
AmsiCloseSession(amsiContext, amsiSession);
}
if (amsiContext)
{
AmsiUninitialize(amsiContext);
}
std::cout << msg << std::endl;
return 0;
}

AMSI API in C#

Using C#, a developer will use InteropServices for the creation of a foreign function interface (FFI). The FFI between C# and C++ is commonly known as a bridge where Managed Code (C#) is safely bridged by the operating system to route calls to Unmanaged Code (C++).

What is Managed Code?

Managed Code is a term reserved for C# development, it basically means that when you write C# code, the execution of such code is constrained to the runtime virtual machine where all code execution and management of resources is handled by the runtime. Things like garbage collection are explict examples of a management action handled by the runtime.

Click To Expand: C# AMSI Conceptual Implementation
amsi_sharp_concept.exe - Applied usage of AMSI API
using System;
using System.Runtime.InteropServices;

/// <summary>
/// Provides native methods for interacting with AMSI
/// (Anti-Malware Scan Interface).
/// </summary>
public static class AmsiNativeMethods
{
private const string AmsiDll = "amsi.dll";

/// <summary>
/// Initializes the AMSI interface.
/// </summary>
/// <param name="appName">The name of the application.</param>
/// <param name="amsiContext">The AMSI context handle.</param>
/// <returns>Returns 0 on success, or an error code on failure.</returns>
[DllImport(
AmsiDll,
EntryPoint = "AmsiInitialize",
CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiInitialize(
[MarshalAs(UnmanagedType.LPWStr)] string appName,
out IntPtr amsiContext);

/// <summary>
/// Uninitializes the AMSI interface.
/// </summary>
/// <param name="amsiContext">The AMSI context handle.</param>
[DllImport(
AmsiDll,
EntryPoint = "AmsiUninitialize",
CallingConvention = CallingConvention.StdCall)]
public static extern void AmsiUninitialize(
IntPtr amsiContext);

/// <summary>
/// Opens an AMSI session.
/// </summary>
/// <param name="amsiContext">The AMSI context handle.</param>
/// <param name="amsiSession">The AMSI session handle.</param>
/// <returns>Returns 0 on success, or an error code on failure.</returns>
[DllImport(
AmsiDll,
EntryPoint = "AmsiOpenSession",
CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiOpenSession(
IntPtr amsiContext,
out IntPtr amsiSession);

/// <summary>
/// Closes an AMSI session.
/// </summary>
/// <param name="amsiContext">The AMSI context handle.</param>
/// <param name="amsiSession">The AMSI session handle.</param>
[DllImport(
AmsiDll,
EntryPoint = "AmsiCloseSession",
CallingConvention = CallingConvention.StdCall)]
public static extern void AmsiCloseSession(
IntPtr amsiContext,
IntPtr amsiSession);

/// <summary>
/// Scans a buffer for malware.
/// </summary>
/// <param name="amsiContext">The AMSI context handle.</param>
/// <param name="buffer">Pointer to the buffer to be scanned.</param>
/// <param name="length">Length of the buffer.</param>
/// <param name="contentName">Name of the content.</param>
/// <param name="amsiSession">The AMSI session handle.</param>
/// <param name="result">Scan result.</param>
/// <returns>Returns 0 on success, or an error code on failure.</returns>
[DllImport(
AmsiDll,
EntryPoint = "AmsiScanBuffer",
CallingConvention = CallingConvention.StdCall)]
public static extern int AmsiScanBuffer(
IntPtr amsiContext,
IntPtr buffer,
uint length,
[MarshalAs(UnmanagedType.LPWStr)] string contentName,
IntPtr amsiSession,
out AMSI_RESULT result);

public enum AMSI_RESULT
{
AMSI_RESULT_CLEAN = 0,
AMSI_RESULT_NOT_DETECTED = 1,
AMSI_RESULT_DETECTED = 32768
}
}

class Program
{
static void Main(string[] args)
{
IntPtr amsiContext;
IntPtr amsiSession;
AmsiNativeMethods.AMSI_RESULT result;

// Initialize AMSI
int hr = AmsiNativeMethods.AmsiInitialize(
"AMSI-DefenderSquad-Scanner",
out amsiContext);
if (hr != 0)
{
Console.WriteLine("AmsiInitialize failed with error: " + hr);
return;
}

// Open a session
hr = AmsiNativeMethods.AmsiOpenSession(
amsiContext,
out amsiSession);
if (hr != 0)
{
Console.WriteLine("AmsiOpenSession failed with error: " + hr);
AmsiNativeMethods.AmsiUninitialize(amsiContext);
return;
}

// The buffer to be scanned
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(
"This is a test buffer to be scanned for malware.");
uint bufferSize = (uint)buffer.Length;

// Allocate unmanaged memory for the buffer
IntPtr bufferPtr = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, bufferPtr, buffer.Length);

// Scan the buffer
hr = AmsiNativeMethods.AmsiScanBuffer(
amsiContext,
bufferPtr,
bufferSize,
"BufferScan",
amsiSession,
out result);
if (hr != 0)
{
Console.WriteLine("AmsiScanBuffer failed with error: " + hr);
}
else
{
if (result == AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED)
{
Console.WriteLine("Malware detected!");
}
else
{
Console.WriteLine("No malware detected.");
}
}

// Free the allocated memory
Marshal.FreeHGlobal(bufferPtr);

// Close the session
AmsiNativeMethods.AmsiCloseSession(
amsiContext,
amsiSession);

// Uninitialize AMSI
AmsiNativeMethods.AmsiUninitialize(amsiContext);

Console.ReadKey();
}
}


AMSI and ETW Events

AMSI and its notifications can be consumed in several ways, remember we talked about AMSI being another visibility event source. Keeping this in mind, AMSI has several ETW providers accessible for experimentation, they are recognizable by the Antimalware keyword as seen in the image below.

ETW Providers

Provider NameEvent Types
Microsoft-Antimalware-AMFilterVarious filtering events related to antimalware activities
Microsoft-Antimalware-EngineEngine-related events including initialization, updates, and operations
Microsoft-Antimalware-ProtectionProtection events related to antimalware mechanisms
Microsoft-Antimalware-RTPReal-time protection events
Microsoft-Antimalware-ServiceService-related events such as on-demand scans, engine updates, cache operations, and Spynet activities
Microsoft-Antimalware-UIUser interface-related events for antimalware software

ETW Event ID 1101

Event ID 1101 is generated by the AMSI ETW Provider - Microsoft-Antimalware-Scan-Interface.

This ETW provider is subscribed to any software program that emits AMSI scans, and specifically, this ETW provider captures the activity of the AmsiScanBuffer function provided by the AMSI Api.

Event ID 1101 Context

FieldContextual Value
AppNameThe name of the application where strings of interest were processed
ContentThe strings (data) scanned
ContentSizeSize in bytes of content field
OriginalSizeSize in bytes of content originally processed
ScanStatusStatus of the scan at the time AmsiScanBuffer is executing
ScanResultStatus (conclusive) verdict based on AMSI Enum Scan Value
e.g., AMSI_RESULT_DETECTED
HashA private hashing implementation for the sequence of bytes seen in the Content field


The image below is a live trace session using the tool Microsoft ETW Message Analyzer. I configure the ETW AMSI Scan Interface provider and start testing with a command-prompt by running powershell commands interactively.

Click Image To Maximize
ETW - AMSI Provider

ETW Test For Event ID 1101

To test for this event, you can use your own or preferred ETW trace monitoring tool like Message Analyzer, and run these commands from a command-prompt.

TESTING - POWERSHELL STREAMS INSPECTED BY AMSI

Open a command-prompt, and run these commands while observing the result of the commands being captured by the ETW provider.

powershell.exe

From Powershell, run this command

gwmi -class win32_computersystem
Click Image To Maximize
ETW - AMSI Provider

References

These are excellent resources to learn more about AMSI.

Community Research

Practical References